/*-------------------------------------------------------------------------
 *
 * tsearchcmds.c
 *
 *	  用于 tsearch 操作命令的例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/commands/tsearchcmds.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <ctype.h>

#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_config_map.h"
#include "catalog/pg_ts_dict.h"
#include "catalog/pg_ts_parser.h"
#include "catalog/pg_ts_template.h"
#include "catalog/pg_type.h"
#include "commands/alter.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "common/string.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "parser/parse_func.h"
#include "tsearch/ts_cache.h"
#include "tsearch/ts_utils.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"

/* 单个由 getTokenTypes() 返回的列表项 */
typedef struct
{
	int			num;			/* 令牌类型编号 */
	char	   *name;			/* 令牌类型名称 */
} TSTokenTypeItem;

static void fc_MakeConfigurationMapping(AlterTSConfigurationStmt *fc_stmt,
									 HeapTuple fc_tup, Relation fc_relMap);
static void fc_DropConfigurationMapping(AlterTSConfigurationStmt *fc_stmt,
									 HeapTuple fc_tup, Relation fc_relMap);
static DefElem *fc_buildDefItem(const char *fc_name, const char *fc_val,
							 bool fc_was_quoted);


/* --------------------- TS Parser 命令 ------------------------ */

/*
 * 查找解析器支持函数并返回其 OID（作为 Datum）
 *
 * attnum 是 pg_ts_parser 列，该函数将放入其中
 */
static Datum fc_get_ts_parser_func(DefElem *fc_defel, int fc_attnum)
{
	List	   *fc_funcName = defGetQualifiedName(fc_defel);
	Oid			fc_typeId[3];
	Oid			fc_retTypeId;
	int			fc_nargs;
	Oid			fc_procOid;

	fc_retTypeId = INTERNALOID;	/* 大多数情况下是正确的 */
	fc_typeId[0] = INTERNALOID;
	switch (fc_attnum)
	{
		case Anum_pg_ts_parser_prsstart:
			fc_nargs = 2;
			fc_typeId[1] = INT4OID;
			break;
		case Anum_pg_ts_parser_prstoken:
			fc_nargs = 3;
			fc_typeId[1] = INTERNALOID;
			fc_typeId[2] = INTERNALOID;
			break;
		case Anum_pg_ts_parser_prsend:
			fc_nargs = 1;
			fc_retTypeId = VOIDOID;
			break;
		case Anum_pg_ts_parser_prsheadline:
			fc_nargs = 3;
			fc_typeId[1] = INTERNALOID;
			fc_typeId[2] = TSQUERYOID;
			break;
		case Anum_pg_ts_parser_prslextype:
			fc_nargs = 1;

			/*
			 * 注意：由于 lextype 方法返回内部类型，因此出于安全原因
			 * 它必须有一个内部类型参数。该参数实际上未被使用，只是
			 * 作为零传递。
			 */
			break;
		default:
			/* 不应该在这里 */
			elog(ERROR, "unrecognized attribute for text search parser: %d",
				 fc_attnum);
			fc_nargs = 0;			/* 保持编译器安静 */
	}

	fc_procOid = LookupFuncName(fc_funcName, fc_nargs, fc_typeId, false);
	if (get_func_rettype(fc_procOid) != fc_retTypeId)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("function %s should return type %s",
						func_signature_string(fc_funcName, fc_nargs, NIL, fc_typeId),
						format_type_be(fc_retTypeId))));

	return ObjectIdGetDatum(fc_procOid);
}

/*
 * 为新的 pg_ts_parser 条目创建 pg_depend 条目
 *
 * 返回值是所述新条目的地址。
 */
static ObjectAddress fc_makeParserDependencies(HeapTuple fc_tuple)
{
	Form_pg_ts_parser fc_prs = (Form_pg_ts_parser) GETSTRUCT(fc_tuple);
	ObjectAddress fc_myself,
				fc_referenced;
	ObjectAddresses *fc_addrs;

	ObjectAddressSet(fc_myself, TSParserRelationId, fc_prs->oid);

	/* 对扩展的依赖 */
	recordDependencyOnCurrentExtension(&fc_myself, false);

	fc_addrs = new_object_addresses();

	/* 对命名空间的依赖 */
	ObjectAddressSet(fc_referenced, NamespaceRelationId, fc_prs->prsnamespace);
	add_exact_object_address(&fc_referenced, fc_addrs);

	/* 对函数的依赖 */  
	ObjectAddressSet(fc_referenced, ProcedureRelationId, fc_prs->prsstart);
	add_exact_object_address(&fc_referenced, fc_addrs);

	fc_referenced.objectId = fc_prs->prstoken;
	add_exact_object_address(&fc_referenced, fc_addrs);

	fc_referenced.objectId = fc_prs->prsend;
	add_exact_object_address(&fc_referenced, fc_addrs);

	fc_referenced.objectId = fc_prs->prslextype;
	add_exact_object_address(&fc_referenced, fc_addrs);

	if (OidIsValid(fc_prs->prsheadline))
	{
		fc_referenced.objectId = fc_prs->prsheadline;
		add_exact_object_address(&fc_referenced, fc_addrs);
	}

	record_object_address_dependencies(&fc_myself, fc_addrs, DEPENDENCY_NORMAL);
	free_object_addresses(fc_addrs);

	return fc_myself;
}

/*
 * 创建文本搜索解析器
 */
ObjectAddress DefineTSParser(List *fc_names, List *fc_parameters)
{
	char	   *fc_prsname;
	ListCell   *fc_pl;
	Relation	fc_prsRel;
	HeapTuple	fc_tup;
	Datum		fc_values[Natts_pg_ts_parser];
	bool		fc_nulls[Natts_pg_ts_parser];
	NameData	fc_pname;
	Oid			fc_prsOid;
	Oid			fc_namespaceoid;
	ObjectAddress fc_address;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to create text search parsers")));

	fc_prsRel = table_open(TSParserRelationId, RowExclusiveLock);

	/* 将名称列表转换为一个名称和命名空间 */
	fc_namespaceoid = QualifiedNameGetCreationNamespace(fc_names, &fc_prsname);

	/* 用名称/命名空间初始化元组字段 */
	memset(fc_values, 0, sizeof(fc_values));
	memset(fc_nulls, false, sizeof(fc_nulls));

	fc_prsOid = GetNewOidWithIndex(fc_prsRel, TSParserOidIndexId,
								Anum_pg_ts_parser_oid);
	fc_values[Anum_pg_ts_parser_oid - 1] = ObjectIdGetDatum(fc_prsOid);
	namestrcpy(&fc_pname, fc_prsname);
	fc_values[Anum_pg_ts_parser_prsname - 1] = NameGetDatum(&fc_pname);
	fc_values[Anum_pg_ts_parser_prsnamespace - 1] = ObjectIdGetDatum(fc_namespaceoid);

	/*
	 * 遍历定义列表并提取我们所需的信息。
	 */
	foreach(fc_pl, fc_parameters)
	{
		DefElem    *fc_defel = (DefElem *) lfirst(fc_pl);

		if (strcmp(fc_defel->defname, "start") == 0)
		{
			fc_values[Anum_pg_ts_parser_prsstart - 1] =
				fc_get_ts_parser_func(fc_defel, Anum_pg_ts_parser_prsstart);
		}
		else if (strcmp(fc_defel->defname, "gettoken") == 0)
		{
			fc_values[Anum_pg_ts_parser_prstoken - 1] =
				fc_get_ts_parser_func(fc_defel, Anum_pg_ts_parser_prstoken);
		}
		else if (strcmp(fc_defel->defname, "end") == 0)
		{
			fc_values[Anum_pg_ts_parser_prsend - 1] =
				fc_get_ts_parser_func(fc_defel, Anum_pg_ts_parser_prsend);
		}
		else if (strcmp(fc_defel->defname, "headline") == 0)
		{
			fc_values[Anum_pg_ts_parser_prsheadline - 1] =
				fc_get_ts_parser_func(fc_defel, Anum_pg_ts_parser_prsheadline);
		}
		else if (strcmp(fc_defel->defname, "lextypes") == 0)
		{
			fc_values[Anum_pg_ts_parser_prslextype - 1] =
				fc_get_ts_parser_func(fc_defel, Anum_pg_ts_parser_prslextype);
		}
		else
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("text search parser parameter \"%s\" not recognized",
							fc_defel->defname)));
	}

	/*
	 * 验证
	 */
	if (!OidIsValid(DatumGetObjectId(fc_values[Anum_pg_ts_parser_prsstart - 1])))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("text search parser start method is required")));

	if (!OidIsValid(DatumGetObjectId(fc_values[Anum_pg_ts_parser_prstoken - 1])))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("text search parser gettoken method is required")));

	if (!OidIsValid(DatumGetObjectId(fc_values[Anum_pg_ts_parser_prsend - 1])))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("text search parser end method is required")));

	if (!OidIsValid(DatumGetObjectId(fc_values[Anum_pg_ts_parser_prslextype - 1])))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("text search parser lextypes method is required")));

	/*
	 * 看起来不错，插入
	 */
	fc_tup = heap_form_tuple(fc_prsRel->rd_att, fc_values, fc_nulls);

	CatalogTupleInsert(fc_prsRel, fc_tup);

	fc_address = fc_makeParserDependencies(fc_tup);

	/* 新文本搜索解析器的创建后钩子 */
	InvokeObjectPostCreateHook(TSParserRelationId, fc_prsOid, 0);

	heap_freetuple(fc_tup);

	table_close(fc_prsRel, RowExclusiveLock);

	return fc_address;
}

/* ---------------------- TS 字典命令 -----------------------*/

/*
 * 为新的 pg_ts_dict 条目创建 pg_depend 条目
 *
 * 返回值是新条目的地址
 */
static ObjectAddress fc_makeDictionaryDependencies(HeapTuple fc_tuple)
{
	Form_pg_ts_dict fc_dict = (Form_pg_ts_dict) GETSTRUCT(fc_tuple);
	ObjectAddress fc_myself,
				fc_referenced;
	ObjectAddresses *fc_addrs;

	ObjectAddressSet(fc_myself, TSDictionaryRelationId, fc_dict->oid);

	/* 对所有者的依赖 */
	recordDependencyOnOwner(fc_myself.classId, fc_myself.objectId, fc_dict->dictowner);

	/* 对扩展的依赖 */
	recordDependencyOnCurrentExtension(&fc_myself, false);

	fc_addrs = new_object_addresses();

	/* 对命名空间的依赖 */
	ObjectAddressSet(fc_referenced, NamespaceRelationId, fc_dict->dictnamespace);
	add_exact_object_address(&fc_referenced, fc_addrs);

	/* 对模板的依赖 */
	ObjectAddressSet(fc_referenced, TSTemplateRelationId, fc_dict->dicttemplate);
	add_exact_object_address(&fc_referenced, fc_addrs);

	record_object_address_dependencies(&fc_myself, fc_addrs, DEPENDENCY_NORMAL);
	free_object_addresses(fc_addrs);

	return fc_myself;
}

/*
 * 验证模板的 init 方法是否接受提议的选项列表
 */
static void fc_verify_dictoptions(Oid fc_tmplId, List *fc_dictoptions)
{
	HeapTuple	fc_tup;
	Form_pg_ts_template fc_tform;
	Oid			fc_initmethod;

	/*
	 * 在独立后端运行时抑制此测试。这是一个
	 * 黑客，以允许 initdb 创建可能在 template1 的编码中
	 * 实际上不能使用的预制字典（由于使用不能
	 * 转换为 template1 的编码的外部文件）。我们希望
	 * 不管怎样创建它们，因为它们可能在其他数据库中
	 * 后来可用。
	 */
	if (!IsUnderPostmaster)
		return;

	fc_tup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(fc_tmplId));
	if (!HeapTupleIsValid(fc_tup)) /* 不应该发生 */
		elog(ERROR, "cache lookup failed for text search template %u",
			 fc_tmplId);
	fc_tform = (Form_pg_ts_template) GETSTRUCT(fc_tup);

	fc_initmethod = fc_tform->tmplinit;

	if (!OidIsValid(fc_initmethod))
	{
		/* 如果没有 init 方法，禁止任何选项 */
		if (fc_dictoptions)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("text search template \"%s\" does not accept options",
							NameStr(fc_tform->tmplname))));
	}
	else
	{
		/*
		 * 复制选项，以防 init 方法认为它可以随意修改
		 * 它们...
		 */
		fc_dictoptions = copyObject(fc_dictoptions);

		/*
		 * 调用 init 方法并查看它是否发出警告。我们不担心
		 * 它泄漏内存，因为我们的命令很快就会结束。
		 */
		(void) OidFunctionCall1(fc_initmethod, PointerGetDatum(fc_dictoptions));
	}

	ReleaseSysCache(fc_tup);
}

/*
 * 创建文本搜索字典
 */
ObjectAddress DefineTSDictionary(List *fc_names, List *fc_parameters)
{
	ListCell   *fc_pl;
	Relation	fc_dictRel;
	HeapTuple	fc_tup;
	Datum		fc_values[Natts_pg_ts_dict];
	bool		fc_nulls[Natts_pg_ts_dict];
	NameData	fc_dname;
	Oid			fc_templId = InvalidOid;
	List	   *fc_dictoptions = NIL;
	Oid			fc_dictOid;
	Oid			fc_namespaceoid;
	AclResult	fc_aclresult;
	char	   *fc_dictname;
	ObjectAddress fc_address;

	/* 将名称列表转换为一个名称和命名空间 */
	fc_namespaceoid = QualifiedNameGetCreationNamespace(fc_names, &fc_dictname);

	/* 检查我们在目标命名空间中是否有创建权限 */
	fc_aclresult = pg_namespace_aclcheck(fc_namespaceoid, GetUserId(), ACL_CREATE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, OBJECT_SCHEMA,
					   get_namespace_name(fc_namespaceoid));

	/*
	 * 遍历定义列表并提取我们所需的信息。
	 */
	foreach(fc_pl, fc_parameters)
	{
		DefElem    *fc_defel = (DefElem *) lfirst(fc_pl);

		if (strcmp(fc_defel->defname, "template") == 0)
		{
			fc_templId = get_ts_template_oid(defGetQualifiedName(fc_defel), false);
		}
		else
		{
			/* 假设这是字典本身的一个选项 */
			fc_dictoptions = lappend(fc_dictoptions, fc_defel);
		}
	}

	/*
	 * 验证
	 */
	if (!OidIsValid(fc_templId))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("text search template is required")));

	fc_verify_dictoptions(fc_templId, fc_dictoptions);


	fc_dictRel = table_open(TSDictionaryRelationId, RowExclusiveLock);

	/*
	 * 看起来不错，插入
	 */
	memset(fc_values, 0, sizeof(fc_values));
	memset(fc_nulls, false, sizeof(fc_nulls));

	fc_dictOid = GetNewOidWithIndex(fc_dictRel, TSDictionaryOidIndexId,
								 Anum_pg_ts_dict_oid);
	fc_values[Anum_pg_ts_dict_oid - 1] = ObjectIdGetDatum(fc_dictOid);
	namestrcpy(&fc_dname, fc_dictname);
	fc_values[Anum_pg_ts_dict_dictname - 1] = NameGetDatum(&fc_dname);
	fc_values[Anum_pg_ts_dict_dictnamespace - 1] = ObjectIdGetDatum(fc_namespaceoid);
	fc_values[Anum_pg_ts_dict_dictowner - 1] = ObjectIdGetDatum(GetUserId());
	fc_values[Anum_pg_ts_dict_dicttemplate - 1] = ObjectIdGetDatum(fc_templId);
	if (fc_dictoptions)
		fc_values[Anum_pg_ts_dict_dictinitoption - 1] =
			PointerGetDatum(serialize_deflist(fc_dictoptions));
	else
		fc_nulls[Anum_pg_ts_dict_dictinitoption - 1] = true;

	fc_tup = heap_form_tuple(fc_dictRel->rd_att, fc_values, fc_nulls);

	CatalogTupleInsert(fc_dictRel, fc_tup);

	fc_address = fc_makeDictionaryDependencies(fc_tup);

	/* 新文本搜索字典的创建后钩子 */
	InvokeObjectPostCreateHook(TSDictionaryRelationId, fc_dictOid, 0);

	heap_freetuple(fc_tup);

	table_close(fc_dictRel, RowExclusiveLock);

	return fc_address;
}

/*
 * ALTER TEXT SEARCH DICTIONARY
 */
ObjectAddress AlterTSDictionary(AlterTSDictionaryStmt *fc_stmt)
{
	HeapTuple	fc_tup,
				fc_newtup;
	Relation	fc_rel;
	Oid			fc_dictId;
	ListCell   *fc_pl;
	List	   *fc_dictoptions;
	Datum		fc_opt;
	bool		fc_isnull;
	Datum		fc_repl_val[Natts_pg_ts_dict];
	bool		fc_repl_null[Natts_pg_ts_dict];
	bool		fc_repl_repl[Natts_pg_ts_dict];
	ObjectAddress fc_address;

	fc_dictId = get_ts_dict_oid(fc_stmt->dictname, false);

	fc_rel = table_open(TSDictionaryRelationId, RowExclusiveLock);

	fc_tup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(fc_dictId));

	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "cache lookup failed for text search dictionary %u",
			 fc_dictId);

	/* 必须是所有者 */
	if (!pg_ts_dict_ownercheck(fc_dictId, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TSDICTIONARY,
					   NameListToString(fc_stmt->dictname));

	/* 反序列化现有选项集 */
	fc_opt = SysCacheGetAttr(TSDICTOID, fc_tup,
						  Anum_pg_ts_dict_dictinitoption,
						  &fc_isnull);
	if (fc_isnull)
		fc_dictoptions = NIL;
	else
		fc_dictoptions = deserialize_deflist(fc_opt);

	/*
	 * 根据指定的更改修改选项列表
	 */
	foreach(fc_pl, fc_stmt->options)
	{
		DefElem    *fc_defel = (DefElem *) lfirst(fc_pl);
		ListCell   *fc_cell;

		/*
		 * 删除任何匹配项...
		 */
		foreach(fc_cell, fc_dictoptions)
		{
			DefElem    *fc_oldel = (DefElem *) lfirst(fc_cell);

			if (strcmp(fc_oldel->defname, fc_defel->defname) == 0)
				fc_dictoptions = foreach_delete_current(fc_dictoptions, fc_cell);
		}

		/*
		 * 如果有新值则添加
		 */
		if (fc_defel->arg)
			fc_dictoptions = lappend(fc_dictoptions, fc_defel);
	}

	/*
	 * 验证
	 */
	fc_verify_dictoptions(((Form_pg_ts_dict) GETSTRUCT(fc_tup))->dicttemplate,
					   fc_dictoptions);

	/*
	 * 看起来不错，更新
	 */
	memset(fc_repl_val, 0, sizeof(fc_repl_val));
	memset(fc_repl_null, false, sizeof(fc_repl_null));
	memset(fc_repl_repl, false, sizeof(fc_repl_repl));

	if (fc_dictoptions)
		fc_repl_val[Anum_pg_ts_dict_dictinitoption - 1] =
			PointerGetDatum(serialize_deflist(fc_dictoptions));
	else
		fc_repl_null[Anum_pg_ts_dict_dictinitoption - 1] = true;
	fc_repl_repl[Anum_pg_ts_dict_dictinitoption - 1] = true;

	fc_newtup = heap_modify_tuple(fc_tup, RelationGetDescr(fc_rel),
							   fc_repl_val, fc_repl_null, fc_repl_repl);

	CatalogTupleUpdate(fc_rel, &fc_newtup->t_self, fc_newtup);

	InvokeObjectPostAlterHook(TSDictionaryRelationId, fc_dictId, 0);

	ObjectAddressSet(fc_address, TSDictionaryRelationId, fc_dictId);

	/*
	 * 注意：因为我们只支持修改选项，而不是模板，
	 * 所以不需要更新依赖项。如果
	 * 选项将来引用数据库内部对象，这可能需要更改。
	 */

	heap_freetuple(fc_newtup);
	ReleaseSysCache(fc_tup);

	table_close(fc_rel, RowExclusiveLock);

	return fc_address;
}

/* ---------------------- TS 模板命令 -----------------------*/

/*
 * 查找模板支持函数并返回其 OID（作为 Datum）
 *
 * attnum 是 pg_ts_template 列，该函数将进入
 */
static Datum fc_get_ts_template_func(DefElem *fc_defel, int fc_attnum)
{
	List	   *fc_funcName = defGetQualifiedName(fc_defel);
	Oid			fc_typeId[4];
	Oid			fc_retTypeId;
	int			fc_nargs;
	Oid			fc_procOid;

	fc_retTypeId = INTERNALOID;
	fc_typeId[0] = INTERNALOID;
	fc_typeId[1] = INTERNALOID;
	fc_typeId[2] = INTERNALOID;
	fc_typeId[3] = INTERNALOID;
	switch (fc_attnum)
	{
		case Anum_pg_ts_template_tmplinit:
			fc_nargs = 1;
			break;
		case Anum_pg_ts_template_tmpllexize:
			fc_nargs = 4;
			break;
		default:
			/* 不应该在这里 */
			elog(ERROR, "unrecognized attribute for text search template: %d",
				 fc_attnum);
			fc_nargs = 0;			/* 保持编译器安静 */
	}

	fc_procOid = LookupFuncName(fc_funcName, fc_nargs, fc_typeId, false);
	if (get_func_rettype(fc_procOid) != fc_retTypeId)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("function %s should return type %s",
						func_signature_string(fc_funcName, fc_nargs, NIL, fc_typeId),
						format_type_be(fc_retTypeId))));

	return ObjectIdGetDatum(fc_procOid);
}

/*
 * 为新的 pg_ts_template 条目制作 pg_depend 记录
 */
static ObjectAddress fc_makeTSTemplateDependencies(HeapTuple fc_tuple)
{
	Form_pg_ts_template fc_tmpl = (Form_pg_ts_template) GETSTRUCT(fc_tuple);
	ObjectAddress fc_myself,
				fc_referenced;
	ObjectAddresses *fc_addrs;

	ObjectAddressSet(fc_myself, TSTemplateRelationId, fc_tmpl->oid);

	/* 对扩展的依赖 */
	recordDependencyOnCurrentExtension(&fc_myself, false);

	fc_addrs = new_object_addresses();

	/* 对命名空间的依赖 */
	ObjectAddressSet(fc_referenced, NamespaceRelationId, fc_tmpl->tmplnamespace);
	add_exact_object_address(&fc_referenced, fc_addrs);

	/* 对函数的依赖 */  
	ObjectAddressSet(fc_referenced, ProcedureRelationId, fc_tmpl->tmpllexize);
	add_exact_object_address(&fc_referenced, fc_addrs);

	if (OidIsValid(fc_tmpl->tmplinit))
	{
		fc_referenced.objectId = fc_tmpl->tmplinit;
		add_exact_object_address(&fc_referenced, fc_addrs);
	}

	record_object_address_dependencies(&fc_myself, fc_addrs, DEPENDENCY_NORMAL);
	free_object_addresses(fc_addrs);

	return fc_myself;
}

/*
 * 创建文本搜索模板
 */
ObjectAddress DefineTSTemplate(List *fc_names, List *fc_parameters)
{
	ListCell   *fc_pl;
	Relation	fc_tmplRel;
	HeapTuple	fc_tup;
	Datum		fc_values[Natts_pg_ts_template];
	bool		fc_nulls[Natts_pg_ts_template];
	NameData	fc_dname;
	int			fc_i;
	Oid			fc_tmplOid;
	Oid			fc_namespaceoid;
	char	   *fc_tmplname;
	ObjectAddress fc_address;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to create text search templates")));

	/* 将名称列表转换为一个名称和命名空间 */
	fc_namespaceoid = QualifiedNameGetCreationNamespace(fc_names, &fc_tmplname);

	fc_tmplRel = table_open(TSTemplateRelationId, RowExclusiveLock);

	for (fc_i = 0; fc_i < Natts_pg_ts_template; fc_i++)
	{
		fc_nulls[fc_i] = false;
		fc_values[fc_i] = ObjectIdGetDatum(InvalidOid);
	}

	fc_tmplOid = GetNewOidWithIndex(fc_tmplRel, TSTemplateOidIndexId,
								 Anum_pg_ts_dict_oid);
	fc_values[Anum_pg_ts_template_oid - 1] = ObjectIdGetDatum(fc_tmplOid);
	namestrcpy(&fc_dname, fc_tmplname);
	fc_values[Anum_pg_ts_template_tmplname - 1] = NameGetDatum(&fc_dname);
	fc_values[Anum_pg_ts_template_tmplnamespace - 1] = ObjectIdGetDatum(fc_namespaceoid);

	/*
	 * 遍历定义列表并提取我们所需的信息。
	 */
	foreach(fc_pl, fc_parameters)
	{
		DefElem    *fc_defel = (DefElem *) lfirst(fc_pl);

		if (strcmp(fc_defel->defname, "init") == 0)
		{
			fc_values[Anum_pg_ts_template_tmplinit - 1] =
				fc_get_ts_template_func(fc_defel, Anum_pg_ts_template_tmplinit);
			fc_nulls[Anum_pg_ts_template_tmplinit - 1] = false;
		}
		else if (strcmp(fc_defel->defname, "lexize") == 0)
		{
			fc_values[Anum_pg_ts_template_tmpllexize - 1] =
				fc_get_ts_template_func(fc_defel, Anum_pg_ts_template_tmpllexize);
			fc_nulls[Anum_pg_ts_template_tmpllexize - 1] = false;
		}
		else
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("text search template parameter \"%s\" not recognized",
							fc_defel->defname)));
	}

	/*
	 * 验证
	 */
	if (!OidIsValid(DatumGetObjectId(fc_values[Anum_pg_ts_template_tmpllexize - 1])))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("text search template lexize method is required")));

	/*
	 * 看起来不错，插入
	 */
	fc_tup = heap_form_tuple(fc_tmplRel->rd_att, fc_values, fc_nulls);

	CatalogTupleInsert(fc_tmplRel, fc_tup);

	fc_address = fc_makeTSTemplateDependencies(fc_tup);

	/* 新文本搜索模板的创建后钩子 */
	InvokeObjectPostCreateHook(TSTemplateRelationId, fc_tmplOid, 0);

	heap_freetuple(fc_tup);

	table_close(fc_tmplRel, RowExclusiveLock);

	return fc_address;
}

/* ---------------------- TS 配置命令 -----------------------*/

/*
 * 查找配置的 syscache 元组。
 * 如果没有这样的 cfg，则返回 NULL。
 */
static HeapTuple fc_GetTSConfigTuple(List *fc_names)
{
	HeapTuple	fc_tup;
	Oid			fc_cfgId;

	fc_cfgId = get_ts_config_oid(fc_names, true);
	if (!OidIsValid(fc_cfgId))
		return NULL;

	fc_tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(fc_cfgId));

	if (!HeapTupleIsValid(fc_tup)) /* 不应该发生 */
		elog(ERROR, "cache lookup failed for text search configuration %u",
			 fc_cfgId);

	return fc_tup;
}

/*
 * 为新的或更新的 pg_ts_config 条目制作 pg_depend 记录
 *
 * 如果配置可能有任何配置映射条目，则传递已打开的 pg_ts_config_map 关系。
 */
static ObjectAddress fc_makeConfigurationDependencies(HeapTuple fc_tuple, bool fc_removeOld,
							  Relation fc_mapRel)
{
	Form_pg_ts_config fc_cfg = (Form_pg_ts_config) GETSTRUCT(fc_tuple);
	ObjectAddresses *fc_addrs;
	ObjectAddress fc_myself,
				fc_referenced;

	fc_myself.classId = TSConfigRelationId;
	fc_myself.objectId = fc_cfg->oid;
	fc_myself.objectSubId = 0;

	/* 对于 ALTER 情况，首先清除旧的依赖关系，除了扩展依赖 */
	if (fc_removeOld)
	{
		deleteDependencyRecordsFor(fc_myself.classId, fc_myself.objectId, true);
		deleteSharedDependencyRecordsFor(fc_myself.classId, fc_myself.objectId, 0);
	}

	/*
	 * 我们使用 ObjectAddresses 列表来移除配置映射信息中可能的重复
	 * 依赖。pg_ts_config 项
	 * 不应该是重复项，但也可以全部折叠到一个调用中。
	 */
	fc_addrs = new_object_addresses();

	/* 对命名空间的依赖 */
	fc_referenced.classId = NamespaceRelationId;
	fc_referenced.objectId = fc_cfg->cfgnamespace;
	fc_referenced.objectSubId = 0;
	add_exact_object_address(&fc_referenced, fc_addrs);

	/* 对所有者的依赖 */
	recordDependencyOnOwner(fc_myself.classId, fc_myself.objectId, fc_cfg->cfgowner);

	/* 对扩展的依赖 */
	recordDependencyOnCurrentExtension(&fc_myself, fc_removeOld);

	/* 对解析器的依赖 */
	fc_referenced.classId = TSParserRelationId;
	fc_referenced.objectId = fc_cfg->cfgparser;
	fc_referenced.objectSubId = 0;
	add_exact_object_address(&fc_referenced, fc_addrs);

	/* 对配置映射中列出的字典的依赖 */
	if (fc_mapRel)
	{
		ScanKeyData fc_skey;
		SysScanDesc fc_scan;
		HeapTuple	fc_maptup;

		/* CCI 以确保我们可以看到调用者更改的影响 */
		CommandCounterIncrement();

		ScanKeyInit(&fc_skey,
					Anum_pg_ts_config_map_mapcfg,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(fc_myself.objectId));

		fc_scan = systable_beginscan(fc_mapRel, TSConfigMapIndexId, true,
								  NULL, 1, &fc_skey);

		while (HeapTupleIsValid((fc_maptup = systable_getnext(fc_scan))))
		{
			Form_pg_ts_config_map fc_cfgmap = (Form_pg_ts_config_map) GETSTRUCT(fc_maptup);

			fc_referenced.classId = TSDictionaryRelationId;
			fc_referenced.objectId = fc_cfgmap->mapdict;
			fc_referenced.objectSubId = 0;
			add_exact_object_address(&fc_referenced, fc_addrs);
		}

		systable_endscan(fc_scan);
	}

	/* 记录它们（包括消除重复） */
	record_object_address_dependencies(&fc_myself, fc_addrs, DEPENDENCY_NORMAL);

	free_object_addresses(fc_addrs);

	return fc_myself;
}

/*
 * 创建文本搜索配置
 */
ObjectAddress DefineTSConfiguration(List *fc_names, List *fc_parameters, ObjectAddress *fc_copied)
{
	Relation	fc_cfgRel;
	Relation	fc_mapRel = NULL;
	HeapTuple	fc_tup;
	Datum		fc_values[Natts_pg_ts_config];
	bool		fc_nulls[Natts_pg_ts_config];
	AclResult	fc_aclresult;
	Oid			fc_namespaceoid;
	char	   *fc_cfgname;
	NameData	fc_cname;
	Oid			fc_sourceOid = InvalidOid;
	Oid			fc_prsOid = InvalidOid;
	Oid			fc_cfgOid;
	ListCell   *fc_pl;
	ObjectAddress fc_address;

	/* 将名称列表转换为一个名称和命名空间 */
	fc_namespaceoid = QualifiedNameGetCreationNamespace(fc_names, &fc_cfgname);

	/* 检查我们在目标命名空间中是否有创建权限 */
	fc_aclresult = pg_namespace_aclcheck(fc_namespaceoid, GetUserId(), ACL_CREATE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, OBJECT_SCHEMA,
					   get_namespace_name(fc_namespaceoid));

	/*
	 * 遍历定义列表并提取我们所需的信息。
	 */
	foreach(fc_pl, fc_parameters)
	{
		DefElem    *fc_defel = (DefElem *) lfirst(fc_pl);

		if (strcmp(fc_defel->defname, "parser") == 0)
			fc_prsOid = get_ts_parser_oid(defGetQualifiedName(fc_defel), false);
		else if (strcmp(fc_defel->defname, "copy") == 0)
			fc_sourceOid = get_ts_config_oid(defGetQualifiedName(fc_defel), false);
		else
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("text search configuration parameter \"%s\" not recognized",
							fc_defel->defname)));
	}

	if (OidIsValid(fc_sourceOid) && OidIsValid(fc_prsOid))
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("cannot specify both PARSER and COPY options")));

	/* 使复制的 tsconfig 对调用者可用 */
	if (fc_copied && OidIsValid(fc_sourceOid))
	{
		ObjectAddressSet(*fc_copied,
						 TSConfigRelationId,
						 fc_sourceOid);
	}

	/*
	 * 如果指定了源配置，则查找源配置。
	 */
	if (OidIsValid(fc_sourceOid))
	{
		Form_pg_ts_config fc_cfg;

		fc_tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(fc_sourceOid));
		if (!HeapTupleIsValid(fc_tup))
			elog(ERROR, "cache lookup failed for text search configuration %u",
				 fc_sourceOid);

		fc_cfg = (Form_pg_ts_config) GETSTRUCT(fc_tup);

		/* 使用源的解析器 */
		fc_prsOid = fc_cfg->cfgparser;

		ReleaseSysCache(fc_tup);
	}

	/*
	 * 验证
	 */
	if (!OidIsValid(fc_prsOid))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("text search parser is required")));

	fc_cfgRel = table_open(TSConfigRelationId, RowExclusiveLock);

	/*
	 * 看起来不错，构建元组并插入
	 */
	memset(fc_values, 0, sizeof(fc_values));
	memset(fc_nulls, false, sizeof(fc_nulls));

	fc_cfgOid = GetNewOidWithIndex(fc_cfgRel, TSConfigOidIndexId,
								Anum_pg_ts_config_oid);
	fc_values[Anum_pg_ts_config_oid - 1] = ObjectIdGetDatum(fc_cfgOid);
	namestrcpy(&fc_cname, fc_cfgname);
	fc_values[Anum_pg_ts_config_cfgname - 1] = NameGetDatum(&fc_cname);
	fc_values[Anum_pg_ts_config_cfgnamespace - 1] = ObjectIdGetDatum(fc_namespaceoid);
	fc_values[Anum_pg_ts_config_cfgowner - 1] = ObjectIdGetDatum(GetUserId());
	fc_values[Anum_pg_ts_config_cfgparser - 1] = ObjectIdGetDatum(fc_prsOid);

	fc_tup = heap_form_tuple(fc_cfgRel->rd_att, fc_values, fc_nulls);

	CatalogTupleInsert(fc_cfgRel, fc_tup);

	if (OidIsValid(fc_sourceOid))
	{
		/*
		 * 从源配置复制 token-dicts 映射
		 */
		ScanKeyData fc_skey;
		SysScanDesc fc_scan;
		HeapTuple	fc_maptup;

		fc_mapRel = table_open(TSConfigMapRelationId, RowExclusiveLock);

		ScanKeyInit(&fc_skey,
					Anum_pg_ts_config_map_mapcfg,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(fc_sourceOid));

		fc_scan = systable_beginscan(fc_mapRel, TSConfigMapIndexId, true,
								  NULL, 1, &fc_skey);

		while (HeapTupleIsValid((fc_maptup = systable_getnext(fc_scan))))
		{
			Form_pg_ts_config_map fc_cfgmap = (Form_pg_ts_config_map) GETSTRUCT(fc_maptup);
			HeapTuple	fc_newmaptup;
			Datum		fc_mapvalues[Natts_pg_ts_config_map];
			bool		fc_mapnulls[Natts_pg_ts_config_map];

			memset(fc_mapvalues, 0, sizeof(fc_mapvalues));
			memset(fc_mapnulls, false, sizeof(fc_mapnulls));

			fc_mapvalues[Anum_pg_ts_config_map_mapcfg - 1] = fc_cfgOid;
			fc_mapvalues[Anum_pg_ts_config_map_maptokentype - 1] = fc_cfgmap->maptokentype;
			fc_mapvalues[Anum_pg_ts_config_map_mapseqno - 1] = fc_cfgmap->mapseqno;
			fc_mapvalues[Anum_pg_ts_config_map_mapdict - 1] = fc_cfgmap->mapdict;

			fc_newmaptup = heap_form_tuple(fc_mapRel->rd_att, fc_mapvalues, fc_mapnulls);

			CatalogTupleInsert(fc_mapRel, fc_newmaptup);

			heap_freetuple(fc_newmaptup);
		}

		systable_endscan(fc_scan);
	}

	fc_address = fc_makeConfigurationDependencies(fc_tup, false, fc_mapRel);

	/* 新文本搜索配置的创建后钩子 */
	InvokeObjectPostCreateHook(TSConfigRelationId, fc_cfgOid, 0);

	heap_freetuple(fc_tup);

	if (fc_mapRel)
		table_close(fc_mapRel, RowExclusiveLock);
	table_close(fc_cfgRel, RowExclusiveLock);

	return fc_address;
}

/*
 * TS 配置删除的核心内容。
 */
void RemoveTSConfigurationById(Oid fc_cfgId)
{
	Relation	fc_relCfg,
				fc_relMap;
	HeapTuple	fc_tup;
	ScanKeyData fc_skey;
	SysScanDesc fc_scan;

	/* 移除 pg_ts_config 条目 */
	fc_relCfg = table_open(TSConfigRelationId, RowExclusiveLock);

	fc_tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(fc_cfgId));

	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "cache lookup failed for text search dictionary %u",
			 fc_cfgId);

	CatalogTupleDelete(fc_relCfg, &fc_tup->t_self);

	ReleaseSysCache(fc_tup);

	table_close(fc_relCfg, RowExclusiveLock);

	/* 移除任何 pg_ts_config_map 条目 */
	fc_relMap = table_open(TSConfigMapRelationId, RowExclusiveLock);

	ScanKeyInit(&fc_skey,
				Anum_pg_ts_config_map_mapcfg,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_cfgId));

	fc_scan = systable_beginscan(fc_relMap, TSConfigMapIndexId, true,
							  NULL, 1, &fc_skey);

	while (HeapTupleIsValid((fc_tup = systable_getnext(fc_scan))))
	{
		CatalogTupleDelete(fc_relMap, &fc_tup->t_self);
	}

	systable_endscan(fc_scan);

	table_close(fc_relMap, RowExclusiveLock);
}

/*
 * ALTER 文本搜索配置 - 主入口点
 */
ObjectAddress AlterTSConfiguration(AlterTSConfigurationStmt *fc_stmt)
{
	HeapTuple	fc_tup;
	Oid			fc_cfgId;
	Relation	fc_relMap;
	ObjectAddress fc_address;

	/* 查找配置 */
	fc_tup = fc_GetTSConfigTuple(fc_stmt->cfgname);
	if (!HeapTupleIsValid(fc_tup))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("text search configuration \"%s\" does not exist",
						NameListToString(fc_stmt->cfgname))));

	fc_cfgId = ((Form_pg_ts_config) GETSTRUCT(fc_tup))->oid;

	/* 必须是所有者 */
	if (!pg_ts_config_ownercheck(fc_cfgId, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TSCONFIGURATION,
					   NameListToString(fc_stmt->cfgname));

	fc_relMap = table_open(TSConfigMapRelationId, RowExclusiveLock);

	/* 添加或删除映射 */
	if (fc_stmt->dicts)
		fc_MakeConfigurationMapping(fc_stmt, fc_tup, fc_relMap);
	else if (fc_stmt->tokentype)
		fc_DropConfigurationMapping(fc_stmt, fc_tup, fc_relMap);

	/* 更新依赖关系 */
	fc_makeConfigurationDependencies(fc_tup, true, fc_relMap);

	InvokeObjectPostAlterHook(TSConfigRelationId, fc_cfgId, 0);

	ObjectAddressSet(fc_address, TSConfigRelationId, fc_cfgId);

	table_close(fc_relMap, RowExclusiveLock);

	ReleaseSysCache(fc_tup);

	return fc_address;
}

/*
 * 检查 token 类型名称是否是 TSTokenTypeItem 列表的成员。
 */
static bool fc_tstoken_list_member(char *fc_token_name, List *fc_tokens)
{
	ListCell   *fc_c;
	bool		fc_found = false;

	foreach(fc_c, fc_tokens)
	{
		TSTokenTypeItem *fc_ts = (TSTokenTypeItem *) lfirst(fc_c);

		if (strcmp(fc_token_name, fc_ts->name) == 0)
		{
			fc_found = true;
			break;
		}
	}

	return fc_found;
}

/*
 * 将 token 类型名称的列表转换为唯一 TSTokenTypeItem 的列表。
 *
 * 重复的条目列表从 tokennames 中移除。
 */
static List * fc_getTokenTypes(Oid fc_prsId, List *fc_tokennames)
{
	TSParserCacheEntry *fc_prs = lookup_ts_parser_cache(fc_prsId);
	LexDescr   *fc_list;
	List	   *fc_result = NIL;
	int			fc_ntoken;
	ListCell   *fc_tn;

	fc_ntoken = list_length(fc_tokennames);
	if (fc_ntoken == 0)
		return NIL;

	if (!OidIsValid(fc_prs->lextypeOid))
		elog(ERROR, "method lextype isn't defined for text search parser %u",
			 fc_prsId);

	/* lextype 接受一个虚拟参数 */
	fc_list = (LexDescr *) DatumGetPointer(OidFunctionCall1(fc_prs->lextypeOid,
														 (Datum) 0));

	foreach(fc_tn, fc_tokennames)
	{
		String	   *fc_val = lfirst_node(String, fc_tn);
		bool		fc_found = false;
		int			fc_j;

		/* 如果该 token 已在结果中，跳过 */
		if (fc_tstoken_list_member(strVal(fc_val), fc_result))
			continue;

		fc_j = 0;
		while (fc_list && fc_list[fc_j].lexid)
		{
			if (strcmp(strVal(fc_val), fc_list[fc_j].alias) == 0)
			{
				TSTokenTypeItem *fc_ts = (TSTokenTypeItem *) palloc0(sizeof(TSTokenTypeItem));

				fc_ts->num = fc_list[fc_j].lexid;
				fc_ts->name = pstrdup(strVal(fc_val));
				fc_result = lappend(fc_result, fc_ts);
				fc_found = true;
				break;
			}
			fc_j++;
		}
		if (!fc_found)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("token type \"%s\" does not exist",
							strVal(fc_val))));
	}

	return fc_result;
}

/*
 * 修改文本搜索配置添加/修改映射
 */
static void fc_MakeConfigurationMapping(AlterTSConfigurationStmt *fc_stmt,
						 HeapTuple fc_tup, Relation fc_relMap)
{
	Form_pg_ts_config fc_tsform;
	Oid			fc_cfgId;
	ScanKeyData fc_skey[2];
	SysScanDesc fc_scan;
	HeapTuple	fc_maptup;
	int			fc_i;
	int			fc_j;
	Oid			fc_prsId;
	List	   *fc_tokens = NIL;
	Oid		   *fc_dictIds;
	int			fc_ndict;
	ListCell   *fc_c;

	fc_tsform = (Form_pg_ts_config) GETSTRUCT(fc_tup);
	fc_cfgId = fc_tsform->oid;
	fc_prsId = fc_tsform->cfgparser;

	fc_tokens = fc_getTokenTypes(fc_prsId, fc_stmt->tokentype);

	if (fc_stmt->override)
	{
		/*
		 * 如果存在并且命令是ALTER，则删除令牌的映射
		 */
		foreach(fc_c, fc_tokens)
		{
			TSTokenTypeItem *fc_ts = (TSTokenTypeItem *) lfirst(fc_c);

			ScanKeyInit(&fc_skey[0],
						Anum_pg_ts_config_map_mapcfg,
						BTEqualStrategyNumber, F_OIDEQ,
						ObjectIdGetDatum(fc_cfgId));
			ScanKeyInit(&fc_skey[1],
						Anum_pg_ts_config_map_maptokentype,
						BTEqualStrategyNumber, F_INT4EQ,
						Int32GetDatum(fc_ts->num));

			fc_scan = systable_beginscan(fc_relMap, TSConfigMapIndexId, true,
									  NULL, 2, fc_skey);

			while (HeapTupleIsValid((fc_maptup = systable_getnext(fc_scan))))
			{
				CatalogTupleDelete(fc_relMap, &fc_maptup->t_self);
			}

			systable_endscan(fc_scan);
		}
	}

	/*
	 * 将字典名称列表转换为字典OIDs数组
	 */
	fc_ndict = list_length(fc_stmt->dicts);
	fc_dictIds = (Oid *) palloc(sizeof(Oid) * fc_ndict);
	fc_i = 0;
	foreach(fc_c, fc_stmt->dicts)
	{
		List	   *fc_names = (List *) lfirst(fc_c);

		fc_dictIds[fc_i] = get_ts_dict_oid(fc_names, false);
		fc_i++;
	}

	if (fc_stmt->replace)
	{
		/*
		 * 替换现有条目中的特定字典
		 */
		Oid			fc_dictOld = fc_dictIds[0],
					fc_dictNew = fc_dictIds[1];

		ScanKeyInit(&fc_skey[0],
					Anum_pg_ts_config_map_mapcfg,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(fc_cfgId));

		fc_scan = systable_beginscan(fc_relMap, TSConfigMapIndexId, true,
								  NULL, 1, fc_skey);

		while (HeapTupleIsValid((fc_maptup = systable_getnext(fc_scan))))
		{
			Form_pg_ts_config_map fc_cfgmap = (Form_pg_ts_config_map) GETSTRUCT(fc_maptup);

			/*
			 * 检查它是否是目标令牌类型之一
			 */
			if (fc_tokens)
			{
				bool		fc_tokmatch = false;

				foreach(fc_c, fc_tokens)
				{
					TSTokenTypeItem *fc_ts = (TSTokenTypeItem *) lfirst(fc_c);

					if (fc_cfgmap->maptokentype == fc_ts->num)
					{
						fc_tokmatch = true;
						break;
					}
				}
				if (!fc_tokmatch)
					continue;
			}

			/*
			 * 如果匹配则替换字典
			 */
			if (fc_cfgmap->mapdict == fc_dictOld)
			{
				Datum		fc_repl_val[Natts_pg_ts_config_map];
				bool		fc_repl_null[Natts_pg_ts_config_map];
				bool		fc_repl_repl[Natts_pg_ts_config_map];
				HeapTuple	fc_newtup;

				memset(fc_repl_val, 0, sizeof(fc_repl_val));
				memset(fc_repl_null, false, sizeof(fc_repl_null));
				memset(fc_repl_repl, false, sizeof(fc_repl_repl));

				fc_repl_val[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(fc_dictNew);
				fc_repl_repl[Anum_pg_ts_config_map_mapdict - 1] = true;

				fc_newtup = heap_modify_tuple(fc_maptup,
										   RelationGetDescr(fc_relMap),
										   fc_repl_val, fc_repl_null, fc_repl_repl);
				CatalogTupleUpdate(fc_relMap, &fc_newtup->t_self, fc_newtup);
			}
		}

		systable_endscan(fc_scan);
	}
	else
	{
		/*
		 * 新条目的插入
		 */
		foreach(fc_c, fc_tokens)
		{
			TSTokenTypeItem *fc_ts = (TSTokenTypeItem *) lfirst(fc_c);

			for (fc_j = 0; fc_j < fc_ndict; fc_j++)
			{
				Datum		fc_values[Natts_pg_ts_config_map];
				bool		fc_nulls[Natts_pg_ts_config_map];

				memset(fc_nulls, false, sizeof(fc_nulls));
				fc_values[Anum_pg_ts_config_map_mapcfg - 1] = ObjectIdGetDatum(fc_cfgId);
				fc_values[Anum_pg_ts_config_map_maptokentype - 1] = Int32GetDatum(fc_ts->num);
				fc_values[Anum_pg_ts_config_map_mapseqno - 1] = Int32GetDatum(fc_j + 1);
				fc_values[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(fc_dictIds[fc_j]);

				fc_tup = heap_form_tuple(fc_relMap->rd_att, fc_values, fc_nulls);
				CatalogTupleInsert(fc_relMap, fc_tup);

				heap_freetuple(fc_tup);
			}
		}
	}

	EventTriggerCollectAlterTSConfig(fc_stmt, fc_cfgId, fc_dictIds, fc_ndict);
}

/*
 * 修改文本搜索配置删除映射
 */
static void fc_DropConfigurationMapping(AlterTSConfigurationStmt *fc_stmt,
						 HeapTuple fc_tup, Relation fc_relMap)
{
	Form_pg_ts_config fc_tsform;
	Oid			fc_cfgId;
	ScanKeyData fc_skey[2];
	SysScanDesc fc_scan;
	HeapTuple	fc_maptup;
	Oid			fc_prsId;
	List	   *fc_tokens = NIL;
	ListCell   *fc_c;

	fc_tsform = (Form_pg_ts_config) GETSTRUCT(fc_tup);
	fc_cfgId = fc_tsform->oid;
	fc_prsId = fc_tsform->cfgparser;

	fc_tokens = fc_getTokenTypes(fc_prsId, fc_stmt->tokentype);

	foreach(fc_c, fc_tokens)
	{
		TSTokenTypeItem *fc_ts = (TSTokenTypeItem *) lfirst(fc_c);
		bool		fc_found = false;

		ScanKeyInit(&fc_skey[0],
					Anum_pg_ts_config_map_mapcfg,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(fc_cfgId));
		ScanKeyInit(&fc_skey[1],
					Anum_pg_ts_config_map_maptokentype,
					BTEqualStrategyNumber, F_INT4EQ,
					Int32GetDatum(fc_ts->num));

		fc_scan = systable_beginscan(fc_relMap, TSConfigMapIndexId, true,
								  NULL, 2, fc_skey);

		while (HeapTupleIsValid((fc_maptup = systable_getnext(fc_scan))))
		{
			CatalogTupleDelete(fc_relMap, &fc_maptup->t_self);
			fc_found = true;
		}

		systable_endscan(fc_scan);

		if (!fc_found)
		{
			if (!fc_stmt->missing_ok)
			{
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_OBJECT),
						 errmsg("mapping for token type \"%s\" does not exist",
								fc_ts->name)));
			}
			else
			{
				ereport(NOTICE,
						(errmsg("mapping for token type \"%s\" does not exist, skipping",
								fc_ts->name)));
			}
		}
	}

	EventTriggerCollectAlterTSConfig(fc_stmt, fc_cfgId, NULL, 0);
}


/*
 * 序列化字典选项，从DefElem列表生成TEXT数据
 *
 * 这用于形成存储在pg_ts_dict.dictinitoption中的值。
 * 为了方便pg_dump，输出的格式与在CREATE TEXT SEARCH DICTIONARY中
 * 重现相同选项时所需的格式完全相同。
 */
text * serialize_deflist(List *fc_deflist)
{
	text	   *fc_result;
	StringInfoData fc_buf;
	ListCell   *fc_l;

	initStringInfo(&fc_buf);

	foreach(fc_l, fc_deflist)
	{
		DefElem    *fc_defel = (DefElem *) lfirst(fc_l);
		char	   *fc_val = defGetString(fc_defel);

		appendStringInfo(&fc_buf, "%s = ",
						 quote_identifier(fc_defel->defname));

		/*
		 * 如果值是T_Integer或T_Float，则不带引号输出，
		 * 否则带引号。这对于允许正确重建节点类型以及值是至关重要的。
		 */
		if (IsA(fc_defel->arg, Integer) || IsA(fc_defel->arg, Float))
			appendStringInfoString(&fc_buf, fc_val);
		else
		{
			/* 如果出现反斜杠，强制使用E语法安全地引用它们 */
			if (strchr(fc_val, '\\'))
				appendStringInfoChar(&fc_buf, ESCAPE_STRING_SYNTAX);
			appendStringInfoChar(&fc_buf, '\'');
			while (*fc_val)
			{
				char		fc_ch = *fc_val++;

				if (SQL_STR_DOUBLE(fc_ch, true))
					appendStringInfoChar(&fc_buf, fc_ch);
				appendStringInfoChar(&fc_buf, fc_ch);
			}
			appendStringInfoChar(&fc_buf, '\'');
		}
		if (lnext(fc_deflist, fc_l) != NULL)
			appendStringInfoString(&fc_buf, ", ");
	}

	fc_result = cstring_to_text_with_len(fc_buf.data, fc_buf.len);
	pfree(fc_buf.data);
	return fc_result;
}

/*
 * 反序列化字典选项，从TEXT重建DefElem列表
 *
 * 这也用于prsheadline选项，因此为了向后兼容，
 * 我们需要接受一些serialize_deflist()永远不会发出的内容：
 * 特别是，未引用和双引号字符串。
 */
List * deserialize_deflist(Datum fc_txt)
{
	text	   *fc_in = DatumGetTextPP(fc_txt);	/* 以防它被烤制 */
	List	   *fc_result = NIL;
	int			fc_len = VARSIZE_ANY_EXHDR(fc_in);
	char	   *fc_ptr,
			   *fc_endptr,
			   *fc_workspace,
			   *fc_wsptr = NULL,
			   *fc_startvalue = NULL;
	typedef enum
	{
		CS_WAITKEY,
		CS_INKEY,
		CS_INQKEY,
		CS_WAITEQ,
		CS_WAITVALUE,
		CS_INSQVALUE,
		CS_INDQVALUE,
		CS_INWVALUE
	} ds_state;
	ds_state	fc_state = CS_WAITKEY;

	fc_workspace = (char *) palloc(fc_len + 1);	/* 确定有足够的空间 */
	fc_ptr = VARDATA_ANY(fc_in);
	fc_endptr = fc_ptr + fc_len;
	for (; fc_ptr < fc_endptr; fc_ptr++)
	{
		switch (fc_state)
		{
			case CS_WAITKEY:
				if (isspace((unsigned char) *fc_ptr) || *fc_ptr == ',')
					continue;
				if (*fc_ptr == '"')
				{
					fc_wsptr = fc_workspace;
					fc_state = CS_INQKEY;
				}
				else
				{
					fc_wsptr = fc_workspace;
					*fc_wsptr++ = *fc_ptr;
					fc_state = CS_INKEY;
				}
				break;
			case CS_INKEY:
				if (isspace((unsigned char) *fc_ptr))
				{
					*fc_wsptr++ = '\0';
					fc_state = CS_WAITEQ;
				}
				else if (*fc_ptr == '=')
				{
					*fc_wsptr++ = '\0';
					fc_state = CS_WAITVALUE;
				}
				else
				{
					*fc_wsptr++ = *fc_ptr;
				}
				break;
			case CS_INQKEY:
				if (*fc_ptr == '"')
				{
					if (fc_ptr + 1 < fc_endptr && fc_ptr[1] == '"')
					{
						/* 只复制两个引号中的一个 */
						*fc_wsptr++ = *fc_ptr++;
					}
					else
					{
						*fc_wsptr++ = '\0';
						fc_state = CS_WAITEQ;
					}
				}
				else
				{
					*fc_wsptr++ = *fc_ptr;
				}
				break;
			case CS_WAITEQ:
				if (*fc_ptr == '=')
					fc_state = CS_WAITVALUE;
				else if (!isspace((unsigned char) *fc_ptr))
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("invalid parameter list format: \"%s\"",
									text_to_cstring(fc_in))));
				break;
			case CS_WAITVALUE:
				if (*fc_ptr == '\'')
				{
					fc_startvalue = fc_wsptr;
					fc_state = CS_INSQVALUE;
				}
				else if (*fc_ptr == 'E' && fc_ptr + 1 < fc_endptr && fc_ptr[1] == '\'')
				{
					fc_ptr++;
					fc_startvalue = fc_wsptr;
					fc_state = CS_INSQVALUE;
				}
				else if (*fc_ptr == '"')
				{
					fc_startvalue = fc_wsptr;
					fc_state = CS_INDQVALUE;
				}
				else if (!isspace((unsigned char) *fc_ptr))
				{
					fc_startvalue = fc_wsptr;
					*fc_wsptr++ = *fc_ptr;
					fc_state = CS_INWVALUE;
				}
				break;
			case CS_INSQVALUE:
				if (*fc_ptr == '\'')
				{
					if (fc_ptr + 1 < fc_endptr && fc_ptr[1] == '\'')
					{
						/* 只复制两个引号中的一个 */
						*fc_wsptr++ = *fc_ptr++;
					}
					else
					{
						*fc_wsptr++ = '\0';
						fc_result = lappend(fc_result,
										 fc_buildDefItem(fc_workspace,
													  fc_startvalue,
													  true));
						fc_state = CS_WAITKEY;
					}
				}
				else if (*fc_ptr == '\\')
				{
					if (fc_ptr + 1 < fc_endptr && fc_ptr[1] == '\\')
					{
						/* 只复制两个反斜杠中的一个 */
						*fc_wsptr++ = *fc_ptr++;
					}
					else
						*fc_wsptr++ = *fc_ptr;
				}
				else
				{
					*fc_wsptr++ = *fc_ptr;
				}
				break;
			case CS_INDQVALUE:
				if (*fc_ptr == '"')
				{
					if (fc_ptr + 1 < fc_endptr && fc_ptr[1] == '"')
					{
						/* 只复制两个引号中的一个 */
						*fc_wsptr++ = *fc_ptr++;
					}
					else
					{
						*fc_wsptr++ = '\0';
						fc_result = lappend(fc_result,
										 fc_buildDefItem(fc_workspace,
													  fc_startvalue,
													  true));
						fc_state = CS_WAITKEY;
					}
				}
				else
				{
					*fc_wsptr++ = *fc_ptr;
				}
				break;
			case CS_INWVALUE:
				if (*fc_ptr == ',' || isspace((unsigned char) *fc_ptr))
				{
					*fc_wsptr++ = '\0';
					fc_result = lappend(fc_result,
									 fc_buildDefItem(fc_workspace,
												  fc_startvalue,
												  false));
					fc_state = CS_WAITKEY;
				}
				else
				{
					*fc_wsptr++ = *fc_ptr;
				}
				break;
			default:
				elog(ERROR, "unrecognized deserialize_deflist state: %d",
					 fc_state);
		}
	}

	if (fc_state == CS_INWVALUE)
	{
		*fc_wsptr++ = '\0';
		fc_result = lappend(fc_result,
						 fc_buildDefItem(fc_workspace,
									  fc_startvalue,
									  false));
	}
	else if (fc_state != CS_WAITKEY)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("invalid parameter list format: \"%s\"",
						text_to_cstring(fc_in))));

	pfree(fc_workspace);

	return fc_result;
}

/*
 * 为deserialize_deflist构建一个DefElem
 */
static DefElem * fc_buildDefItem(const char *fc_name, const char *fc_val, bool fc_was_quoted)
{
	/* 如果输入被引用，总是作为字符串输出 */
	if (!fc_was_quoted && fc_val[0] != '\0')
	{
		int			fc_v;
		char	   *fc_endptr;

		/* 尝试解析为整数 */
		errno = 0;
		fc_v = strtoint(fc_val, &fc_endptr, 10);
		if (errno == 0 && *fc_endptr == '\0')
			return makeDefElem(pstrdup(fc_name),
							   (Node *) makeInteger(fc_v),
							   -1);
		/* 不行，那尝试作为浮点数？ */
		errno = 0;
		(void) strtod(fc_val, &fc_endptr);
		if (errno == 0 && *fc_endptr == '\0')
			return makeDefElem(pstrdup(fc_name),
							   (Node *) makeFloat(pstrdup(fc_val)),
							   -1);

		if (strcmp(fc_val, "true") == 0)
			return makeDefElem(pstrdup(fc_name),
							   (Node *) makeBoolean(true),
							   -1);
		if (strcmp(fc_val, "false") == 0)
			return makeDefElem(pstrdup(fc_name),
							   (Node *) makeBoolean(false),
							   -1);
	}
	/* 就把它当作字符串处理 */
	return makeDefElem(pstrdup(fc_name),
					   (Node *) makeString(pstrdup(fc_val)),
					   -1);
}
